/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <iomanip>
#include <string>
#include <sstream>
#include "ncsi_over_mctp_wx.h"
#include <ncsi/ncsi_protocol.h>
#include <ncsi/ncsi_wx.h>
#include "mc/dict.h"
#include "mc/variant.h"
#include "mc/log.h"

namespace dev {

bool check_response_data(mc::dict ctx, oem_wx_common_rsp_t *rsp, uint8_t command_id)
{
    uint16_t response_code = SHORT_BY_BIG_ENDIAN(rsp->response_code);
    uint16_t reason_code   = SHORT_BY_BIG_ENDIAN(rsp->reason_code);
    // todo: response_code不为成功时，需要加上reason_code的错误码映射
    if (response_code != WX_RESP_CODE_SUCCESS) {
        elog("unsuccessful request with response code: ${code}, reason code: ${reason}", 
            ("code", response_code)("reason", reason_code));
        return false;
    }
    if (rsp->manufacturer_id != RSP_WX_MANUFACTURE_ID) {
        elog("invalid response data with wrong manufacturer id: ${manufacturer_id}", ("manufacturer_id", rsp->manufacturer_id));
        return false;
    }
    if (rsp->command_id != command_id) {
        elog("invalid response data with wrong command id: ${command_id}, expected: ${expected}", 
            ("command_id", rsp->command_id)("expected", command_id));
        return false;
    }
    return true;
}

mc::dict get_oem_command_ctx_wx(uint8_t channel_id, uint8_t package_id)
{
    mc::dict ctx({
        {"PacketType", OEM_COMMAND},
        {"ExpectRspPacketType", OEM_COMMAND_RSP},
        {"ChannelId", channel_id},
        {"PackageId", package_id}
    });
    return ctx;
}

bool ncsi_over_mctp_wx::get_temperature(uint8_t package_id, uint8_t channel_id, uint16_t *temp_info)
{
    if (temp_info == nullptr) {
        elog("Error: temperature info pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_TEMPERATURE);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);

    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get temperature command");
        return false;
    }

    if (rsp_str.empty() || rsp_str.size() < 12) {
        elog("Error: invalid temperature response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 12));
        return false;
    }

    oem_wx_temperature_rsp_t* rsp = reinterpret_cast<oem_wx_temperature_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_TEMPERATURE)) {
        return false;
    }

    *temp_info = rsp->cur_temp;

    return true;
}

bool ncsi_over_mctp_wx::get_mac_address(uint8_t package_id, uint8_t channel_id, uint8_t *mac_addr, uint8_t len)
{
    if (mac_addr == nullptr) {
        elog("Error: mac address pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_MAC_ADDRESS);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);

    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get MAC address command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 16) {
        elog("Error: invalid mac address response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 16));
        return false;
    }

    oem_wx_mac_address_rsp_t* rsp = reinterpret_cast<oem_wx_mac_address_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_MAC_ADDRESS)) {
        return false;
    }
    memcpy(mac_addr, rsp->mac_addr, len);

    return true;
}

bool ncsi_over_mctp_wx::get_serial_number(uint8_t package_id, uint8_t channel_id, std::string &sn_info)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_SERIAL_NUMBER);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get serial number command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 19) {
        elog("Error: invalid serial number response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 19));
        return false;
    }

    oem_wx_serial_number_rsp_t* rsp = reinterpret_cast<oem_wx_serial_number_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_SERIAL_NUMBER)) {
        return false;
    }

    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (size_t i = 0; i < WX_SERIAL_NUMBER_MAX_LEN; i++) {
        ss << std::setw(2) << (int)rsp->serial_number[WX_SERIAL_NUMBER_MAX_LEN - 1 - i];
    }
    sn_info = ss.str();
    return true;
}

bool ncsi_over_mctp_wx::enable_lldp(uint8_t package_id, uint8_t channel_id)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_ENABLE_LLDP);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI enable LLDP command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 11) {
        elog("Error: invalid enable LLDP response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 11));
        return false;
    }

    oem_wx_enable_lldp_rsp_t* rsp = reinterpret_cast<oem_wx_enable_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_ENABLE_LLDP)) {
        return false;
    }

    // 检查操作是否成功
    if (rsp->lldp_capability != true) {
        elog("enable lldp failed");
        return false;
    }
    return true;
}

bool ncsi_over_mctp_wx::disable_lldp(uint8_t package_id, uint8_t channel_id)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_DISABLE_LLDP);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI disable LLDP command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 11) {
        elog("Error: invalid disable LLDP response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 11));
        return false;
    }

    oem_wx_disable_lldp_rsp_t* rsp = reinterpret_cast<oem_wx_disable_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_DISABLE_LLDP)) {
        return false;
    }

    // 检查操作是否成功
    if (rsp->lldp_capability != false) {
        elog("disable lldp failed");
        return false;
    }

    return true;
}

bool ncsi_over_mctp_wx::get_lldp_capability(uint8_t package_id, uint8_t channel_id, uint8_t *lldp_capability)
{
    if (lldp_capability == nullptr) {
        elog("Error: lldp_capability pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_LLDP_CAPABILITY);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get LLDP status command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 11) {
        elog("Error: invalid get LLDP capability response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 11));
        return false;
    }

    oem_wx_get_lldp_rsp_t* rsp = reinterpret_cast<oem_wx_get_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_LLDP_CAPABILITY)) {
        return false;
    }

    *lldp_capability = rsp->lldp_capability;

    return true;
}

bool ncsi_over_mctp_wx::set_lldp(uint8_t package_id, uint8_t channel_id, uint8_t enable_flag)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);

    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_SET_LLDP);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    data.insert(data.end(), enable_flag);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI set LLDP status command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() < 11) {
        elog("Error: invalid set LLDP response size, ${size}, ${expected}", ("size", rsp_str.size())("expected", 11));
        return false;
    }

    oem_wx_set_lldp_rsp_t* rsp = reinterpret_cast<oem_wx_set_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_SET_LLDP)) {
        return false;
    }
    if (rsp->lldp_capability != enable_flag) {
        elog("set lldp failed, op: ${enable}", ("enable", enable_flag));
        return false;
    }

    return true;
}

bool ncsi_over_mctp_wx::get_sff_info(uint8_t package_id, uint8_t channel_id, wx_ncsi_sff_info_t *sff_info)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);
    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_SFF_INFO);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get_sff_info command");
        return false;
    }
    if (rsp_str.empty()) {
        elog("Error: cannot get sff info");
        return false;
    }

    oem_wx_get_sff_info_rsp_t* rsp = reinterpret_cast<oem_wx_get_sff_info_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_SFF_INFO)) {
        return false;
    }

    memcpy(sff_info, &(rsp->temp_high_alarm), sizeof(wx_ncsi_sff_info_t));

    return true;
}

bool ncsi_over_mctp_wx::get_vpd_sn(uint8_t package_id, uint8_t channel_id, uint8_t *vpd_sn, uint8_t *sn_len)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);
    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_VPD_SN);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get_vpd_sn command");
        return false;
    }
    if (rsp_str.empty()) {
        elog("Error: cannot get vpd sn");
        return false;
    }

    oem_wx_get_vpd_sn_rsp_t* rsp = reinterpret_cast<oem_wx_get_vpd_sn_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_VPD_SN)) {
        return false;
    }

    memcpy(vpd_sn, rsp->sn, rsp->sn_len);
    *sn_len = rsp->sn_len;

    return true;
}

bool ncsi_over_mctp_wx::get_vpd_pn(uint8_t package_id, uint8_t channel_id, uint8_t *vpd_pn, uint8_t *pn_len)
{
    mc::dict ctx = get_oem_command_ctx_wx(channel_id, package_id);
    uint8_t data_array[WX_COMMON_DATA_SIZE] = WX_COMMON_DATA(WX_OEM_CMD_REV, WX_CMD_GET_VPD_PN);
    std::vector<uint8_t> data(data_array, data_array + WX_COMMON_DATA_SIZE);
    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        elog("Error: failed to send NCSI get_vpd_pn command");
        return false;
    }
    if (rsp_str.empty()) {
        elog("Error: cannot get vpd pn");
        return false;
    }

    oem_wx_get_vpd_pn_rsp_t* rsp = reinterpret_cast<oem_wx_get_vpd_pn_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(ctx, reinterpret_cast<oem_wx_common_rsp_t *>(rsp), WX_CMD_GET_VPD_PN)) {
        return false;
    }

    memcpy(vpd_pn, rsp->pn, rsp->pn_len);
    *pn_len = rsp->pn_len;

    return true;
}

} // namespace dev 